package param

import (
	"errors"
	"fmt"
	"mime/multipart"
	"path/filepath"
	"reflect"
	"regexp"
	"strings"
)

// 参数分割符
const Separator = " "

// 参数标签
const (
	TagID       = "id"
	TagRequired = "required"
	TagNum      = "num"
	TagLen      = "len"
	TagItem     = "item"
	TagEmail    = "email"
	TagEnum     = "enum"
	TagRegexp   = "reg"
	TagFileKB   = "size"
	TagFileExt  = "exts"
	TagFileType = "mime"
)

var emailPattern = "[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?"
var emailExp = regexp.MustCompile(emailPattern)

type condition struct {
	id string // id may be empty. for recording the input id, point out witch input got the message

	required *string // nil means not required

	// number condition
	maxNum   *float64
	eqMaxNum bool
	minNum   *float64
	eqMinNum bool
	numMsgID *string

	// string condition
	maxLen   *int // max []rune length
	minLen   *int // min []rune length
	lenMsgID *string

	// file size condition
	maxFileByte     *int64
	minFileByte     *int64
	fileSizeMsgID *string

	// file extension condition
	fileExtensions []string
	fileExtMsgID   *string

	// file mime type condition
	fileMimeTypes []string
	fileMimeMsgID *string

	// slice condition
	maxItem   *int
	minItem   *int
	itemMsgID *string

	// enum condition
	enums     []string
	enumMsgID *string

	// regular condition
	pattern  *string
	regexp   *regexp.Regexp
	regMsgID *string
}

func (c *condition) SetID(id string) *condition {
	c.id = id
	return c
}

func (c *condition) Required(msg string) *condition {
	c.required = &msg
	return c
}

func (c *condition) Len(min, max int, msg string) *condition {
	c.minLen = &min
	c.maxLen = &max
	c.lenMsgID = &msg
	return c
}

func (c *condition) Email(msg string) *condition {
	c.pattern = &emailPattern
	c.regexp = emailExp
	c.regMsgID = &msg
	return c
}

func (c *condition) Regexp(pattern, msg string) *condition {
	c.pattern = &pattern
	c.regexp = regexp.MustCompile(pattern)
	c.regMsgID = &msg
	return c
}

func (c *condition) Item(min, max int, msg string) *condition {
	c.minItem = &min
	c.maxItem = &max
	c.itemMsgID = &msg
	return c
}

func (c *condition) Enums(elements []string, msg string) *condition {
	c.enums = elements
	c.enumMsgID = &msg
	return c
}

// set MIME type condition, the condition could be a specific
// type(e.g. image/png image/jpeg text/plain application/octet-stream)
// or just the main type(e.g. image text application)
func (c *condition) FileMimeTypes(types []string, msg string) *condition {
	c.fileMimeTypes = types
	c.fileMimeMsgID = &msg
	return c
}

// 限制文件大小, 单位: MB
func (c *condition) FileSizeMB(min, max int64, msg string) *condition {
	min = min << 10
	max = max << 10
	return c.FileSizeKB(min, max, msg)
}

// 限制文件大小, 单位: KB, tag 标签中设置的文件大小限制默认调用此方法.
// (e.g. size:"20-500" size-msg:"20-500KB")
func (c *condition) FileSizeKB(min, max int64, msg string) *condition {
	min = min << 10
	max = max << 10
	return c.FileSizeByte(min, max, msg)
}

// 限制文件大小, 单位: Byte
func (c *condition) FileSizeByte(min, max int64, msg string) *condition {
	c.minFileByte = &min
	c.maxFileByte = &max
	c.fileSizeMsgID = &msg
	return c
}

func (c *condition) FileExts(exts []string, msg string) *condition {
	c.fileExtensions = exts
	c.fileExtMsgID = &msg
	return c
}

func (c *condition) MaxNum(max float64, eq bool) *condition {
	c.maxNum = &max
	c.eqMaxNum = eq
	return c
}

func (c *condition) MinNum(min float64, eq bool) *condition {
	c.minNum = &min
	c.eqMinNum = eq
	return c
}

func (c *condition) NumMsg(msg string) *condition {
	c.numMsgID = &msg
	return c
}

func isCondition(tag reflect.StructTag) bool {
	if _, ok := tag.Lookup(TagRequired); ok {
		return true
	}
	if _, ok := tag.Lookup(TagNum); ok {
		return true
	}
	if _, ok := tag.Lookup(TagLen); ok {
		return true
	}
	if _, ok := tag.Lookup(TagItem); ok {
		return true
	}
	if _, ok := tag.Lookup(TagEmail); ok {
		return true
	}
	if _, ok := tag.Lookup(TagEnum); ok {
		return true
	}
	if _, ok := tag.Lookup(TagRegexp); ok {
		return true
	}
	if _, ok := tag.Lookup(TagFileKB); ok {
		return true
	}
	if _, ok := tag.Lookup(TagFileExt); ok {
		return true
	}
	if _, ok := tag.Lookup(TagFileType); ok {
		return true
	}
	return false
}

func (c *condition) Syntax(syntax string) error {
	tag := reflect.StructTag(syntax)
	if id, ok := tag.Lookup(TagID); ok {
		c.SetID(id)
	}
	if required, ok := tag.Lookup(TagRequired); ok {
		if required == "" {
			return errors.New("required msg is empty")
		}
		c.Required(required)
	}
	if syntax, ok := tag.Lookup(TagLen); ok {
		min, max, e := readLength(syntax)
		if e != nil {
			return e
		}
		msg := tag.Get(MsgName(TagLen))
		if msg == "" {
			return fmt.Errorf("%s msg is empty", TagLen)
		}
		c.Len(min, max, msg)
	}
	if pattern, ok := tag.Lookup(TagRegexp); ok {
		msg := tag.Get(MsgName(TagRegexp))
		if msg == "" {
			return fmt.Errorf("%s msg is empty", TagRegexp)
		}
		c.Regexp(pattern, msg)
	}
	if msg, ok := tag.Lookup(TagEmail); ok {
		if msg == "" {
			return fmt.Errorf("%s msg is empty", TagEmail)
		}
		c.Email(msg)
	}
	if syntax, ok := tag.Lookup(TagFileKB); ok {
		min, max, e := readLength(syntax)
		if e != nil {
			return e
		}
		msg := tag.Get(MsgName(TagFileKB))
		if msg == "" {
			return fmt.Errorf("%s msg is empty", TagFileKB)
		}
		c.FileSizeKB(int64(min), int64(max), msg)
	}
	if syntax, ok := tag.Lookup(TagFileType); ok {
		types := strings.Split(syntax, Separator)
		for key, typ := range types {
			types[key] = strings.TrimSpace(typ)
		}
		msg := tag.Get(MsgName(TagFileType))
		if msg == "" {
			return fmt.Errorf("%s msg is empty", TagFileType)
		}
		c.FileMimeTypes(types, msg)
	}
	if syntax, ok := tag.Lookup(TagFileExt); ok {
		exts := strings.Split(syntax, Separator)
		for key, ext := range exts {
			exts[key] = strings.TrimSpace(ext)
		}
		msg := tag.Get(MsgName(TagFileExt))
		if msg == "" {
			return fmt.Errorf("%s msg is empty", TagFileExt)
		}
		c.FileExts(exts, msg)
	}
	if syntax, ok := tag.Lookup(TagNum); ok {
		min, max, eqMin, eqMax, e := readArea(syntax)
		if e != nil {
			return e
		}
		msg := tag.Get(MsgName(TagNum))
		if msg == "" {
			return fmt.Errorf("%s msg is empty", TagNum)
		} else {
			c.NumMsg(msg)
		}
		if min != nil {
			c.MinNum(*min, eqMin)
		}
		if max != nil {
			c.MaxNum(*max, eqMax)
		}
	}
	if syntax, ok := tag.Lookup(TagItem); ok {
		min, max, e := readLength(syntax)
		if e != nil {
			return e
		}
		msg := tag.Get(MsgName(TagItem))
		if msg == "" {
			return fmt.Errorf("%s msg is empty", TagItem)
		}
		c.Item(min, max, msg)
	}
	if syntax, ok := tag.Lookup(TagEnum); ok {
		enums := strings.Split(syntax, Separator)
		for key, enum := range enums {
			enums[key] = strings.TrimSpace(enum)
		}
		msg := tag.Get(MsgName(TagEnum))
		if msg == "" {
			return fmt.Errorf("%s msg is empty", TagEnum)
		}
		c.Enums(enums, msg)
	}
	return nil
}

type ConditionProvider interface {
	Info() *info
}

func (c *condition) Info() *info {
	if c == nil {
		return &info{}
	}
	return &info{
		ID:       c.id,
		Required: c.required,
		MaxNum:   c.maxNum,
		EqMaxNum: c.eqMaxNum,
		MinNum:   c.minNum,
		EqMinNum: c.eqMinNum,
		NumMsgID: c.numMsgID,
		// string condition
		MaxLen:   c.maxLen,
		MinLen:   c.minLen,
		LenMsgID: c.lenMsgID,

		// file size condition
		MaxFileByte:     c.maxFileByte,
		MinFileByte:     c.minFileByte,
		FileSizeMsgID: c.fileSizeMsgID,

		// file mime type condition
		FileMimeTypes: c.fileMimeTypes,
		FileMimeMsgID: c.fileMimeMsgID,

		// file extension condition
		FileExtensions: c.fileExtensions,
		FileExtMsgID:   c.fileExtMsgID,

		// slice condition
		MaxItem:   c.maxItem,
		MinItem:   c.minItem,
		ItemMsgID: c.itemMsgID,

		// enum condition
		Enums:     c.enums,
		EnumMsgID: c.enumMsgID,

		// regular condition
		Pattern:  c.pattern,
		RegMsgID: c.regMsgID,
	}
}

type info struct {
	ID string // id may be empty. for recording the input id, point out witch input got the message

	Required *string // nil means not required

	// number condition
	MaxNum   *float64
	EqMaxNum bool
	MinNum   *float64
	EqMinNum bool
	NumMsgID *string

	// string condition
	MaxLen   *int // max []rune length
	MinLen   *int // min []rune length
	LenMsgID *string

	// file size condition
	MaxFileByte     *int64
	MinFileByte     *int64
	FileSizeMsgID *string

	// file mime type condition
	FileMimeTypes []string
	FileMimeMsgID *string

	// file extension condition
	FileExtensions []string
	FileExtMsgID   *string

	// slice condition
	MaxItem   *int
	MinItem   *int
	ItemMsgID *string

	// enum condition
	Enums     []string
	EnumMsgID *string

	// regular condition
	Pattern  *string
	RegMsgID *string
}

type Validator struct {
	ptr uintptr
	conditions map[uintptr]*condition
}

func (v *Validator) offsetField(offset uintptr) *condition {

		c := &condition{}
		v.conditions[offset] = c
		return c
}

func (v *Validator) Field(pointer interface{}) *condition {
	ptr := reflect.ValueOf(pointer).Pointer()
	offset := ptr - v.ptr
	return v.offsetField(offset)
}

func NewEmptyValidator(schema interface{}) *Validator {
	return &Validator{
		ptr:reflect.ValueOf(schema).Pointer(),
		conditions:make(map[uintptr]*condition),
	}
}

func (c *condition) validStr(field string, value string) (msg, id string) {
	if c.required == nil && value == "" {
		return
	}

	if c.required != nil && value == "" {
		return *c.required, c.id
	}

	if reg := c.regexp; reg != nil {
		if !reg.MatchString(value) {
			return *c.regMsgID, c.id
		}
	}

	ln := len([]rune(value))
	if max := c.maxLen; max != nil {
		if ln > *max {
			return *c.lenMsgID, c.id
		}
	}
	if min := c.minLen; min != nil {
		if ln < *min {
			return *c.lenMsgID, c.id
		}
	}
	return
}

func (c *condition) validNum(field string, valueStr string, value float64) (msg, id string) {
	if c.required == nil && valueStr == "" {
		return
	}

	if c.required != nil && valueStr == "" {
		return *c.required, c.id
	}

	if c.minNum != nil {
		if c.eqMinNum {
			if value < *c.minNum {
				return *c.numMsgID, c.id
			}
		} else {
			if value <= *c.minNum {
				return *c.numMsgID, c.id
			}
		}
	}
	if c.maxNum != nil {
		if c.eqMaxNum {
			if value > *c.maxNum {
				return *c.numMsgID, c.id
			}
		} else {
			if value >= *c.maxNum {
				return *c.numMsgID, c.id
			}
		}
	}
	return
}

func (c *condition) validFile(field string, form multipart.Form) (msg, id string) {
	fileItem := len(form.File[field])
	if c.required == nil && fileItem == 0 {
		return
	}
	if c.required != nil && fileItem == 0 {
		return *c.required, c.id
	}
	if c.minItem != nil {
		if *c.minItem > fileItem {
			return *c.itemMsgID, c.id
		}
	}
	if c.maxItem != nil {
		if *c.maxItem < fileItem {
			return *c.itemMsgID, c.id
		}
	}
	if c.maxFileByte != nil {
		maxSize := *c.maxFileByte
		for _, header := range form.File[field] {
			if header.Size > maxSize {
				return *c.fileSizeMsgID, c.id
			}
		}
	}
	if c.minFileByte != nil {
		minSize := *c.minFileByte
		for _, header := range form.File[field] {
			if header.Size < minSize {
				return *c.fileSizeMsgID, c.id
			}
		}
	}
	if len(c.fileExtensions) > 0 {
		for _, header := range form.File[field] {
			exist := false
			for _, ext := range c.fileExtensions {
				if filepath.Ext(header.Filename) == ext {
					exist = true
					break
				}
			}
			if !exist {
				return *c.fileExtMsgID, c.id
			}
		}
	}
	// MIME format: MainType/SubType
	if len(c.fileMimeTypes) > 0 {
		for _, header := range form.File[field] {
			exist := false
			for _, need := range c.fileMimeTypes {
				got := header.Header.Get("Content-Type")
				if got == need {
					exist = true
					break
				} else {
					if !strings.Contains(need, "/") { // e.g. need = "image" got = "image/png"
						if need == got[:strings.Index(got, "/")] {
							exist = true
							break
						}
					}
				}
			}
			if !exist {
				return *c.fileMimeMsgID, c.id
			}
		}
	}
	return
}

func (c *condition) validItem(field string, form multipart.Form) (msg, id string) {
	item := len(form.Value[field])
	if c.required == nil && item == 0 {
		return
	}
	if c.required != nil && item == 0 {
		return *c.required, c.id
	}
	if c.minItem != nil {
		if *c.minItem > item {
			return *c.itemMsgID, c.id
		}
	}
	if c.maxItem != nil {
		if *c.maxItem < item {
			return *c.itemMsgID, c.id
		}
	}
	return
}

func (c *condition) validTime(field string, form multipart.Form) (msg, id string) {
	item := len(form.Value[field])
	if c.required == nil && item == 0 {
		return
	}
	if c.required != nil && item == 0 {
		return *c.required, c.id
	}
	return
}

func (c *condition) validEnum(field string, form multipart.Form) (msg, id string) {
	values := form.Value[field]
	if c.required == nil && len(values) == 0 {
		return
	}
	if c.required != nil && len(values) == 0 {
		return *c.required, c.id
	}
	if len(c.enums) > 0 {
		for _, value := range values {
			got := false
			for _, e := range c.enums {
				if value == e {
					got = true
				}
			}
			if !got {
				return *c.enumMsgID, c.id
			}
		}
	}
	return
}
